/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
 */
/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <hilog/log.h>
#include "multimedia/player_framework/native_avcodec_videoencoder.h"
#include "multimedia/player_framework/native_avbuffer_info.h"
#include "SampleInfo.h"
#include "SampleCallback.h"
#include "AVCodecSampleError.h"
#include "VideoEncoder.h"

#undef LOG_TAG
#define LOG_TAG "VideoEncoder"

VideoEncoder::~VideoEncoder() { Release(); }

int32_t VideoEncoder::Create(const std::string &videoCodecMime)
{
    encoder_ = OH_VideoEncoder_CreateByMime(videoCodecMime.c_str());
    OH_LOG_INFO(LOG_APP, "videoCodecMime.c_str() = %{public}s", videoCodecMime.c_str());
    if (encoder_ == nullptr) {
        OH_LOG_ERROR(LOG_APP, "Create failed");
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t VideoEncoder::Config(SampleInfo &sampleInfo, CodecUserData *codecUserData)
{
    if (encoder_ == nullptr) {
        OH_LOG_ERROR(LOG_APP, "Encoder is null");
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    if (codecUserData == nullptr) {
        OH_LOG_ERROR(LOG_APP, "Invalid param: codecUserData");
        return AVCODEC_SAMPLE_ERR_ERROR;
    }

    int32_t ret = Configure(sampleInfo);
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "Configure failed");
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    
    ret = SetCallback(codecUserData);
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "Set callback failed, ret: %{public}d", ret);
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    
    ret = OH_VideoEncoder_Prepare(encoder_);
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "Prepare failed, ret: %{public}d", ret);
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t VideoEncoder::Start()
{
    if (encoder_ == nullptr) {
        OH_LOG_ERROR(LOG_APP, "Encoder is null");
        return AVCODEC_SAMPLE_ERR_ERROR;
    }

    int ret = OH_VideoEncoder_Start(encoder_);
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "Start failed, ret: %{public}d", ret);
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t VideoEncoder::PushInputData(CodecBufferInfo &info)
{
    if (encoder_ == nullptr) {
        OH_LOG_ERROR(LOG_APP, "Encoder is null");
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    if (info.buffer == 0 || info.bufferIndex < 0) {
        OH_LOG_ERROR(LOG_APP, "Invalid buffer or index");
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    OH_LOG_INFO(LOG_APP, "In PushInputData: size = %{public}d, pts = %{public}ld",
                info.attr.size, info.attr.pts);
    int32_t ret = OH_VideoEncoder_PushInputBuffer(encoder_, info.bufferIndex);
    OH_LOG_INFO(LOG_APP, "Set OH_AudioCodec_PushInputBuffer, ret: %{public}d", ret);
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "PushInputBuffer: index=%{public}u, size=%{public}d, pts=%{public}ld, flags=%{public}u",
            info.bufferIndex, info.attr.size, info.attr.pts, info.attr.flags);
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t VideoEncoder::FreeOutputBuffer(uint32_t bufferIndex)
{
    if (encoder_ == nullptr) {
        OH_LOG_ERROR(LOG_APP, "Encoder is null");
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    int32_t ret = AVCODEC_SAMPLE_ERR_OK;
    ret = OH_VideoEncoder_FreeOutputBuffer(encoder_, bufferIndex);
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "Free output data failed, ret: %{public}d", ret);
        return AVCODEC_SAMPLE_ERR_ERROR;
    }

    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t VideoEncoder::Stop()
{
    if (encoder_ == nullptr) {
        OH_LOG_ERROR(LOG_APP, "Encoder is null");
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    
    int ret = OH_VideoEncoder_Flush(encoder_);
    if (ret != AV_ERR_OK) {
        OH_LOG_WARN(LOG_APP, "Flush failed, but continuing with stop: %{public}d", ret);
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    ret = OH_VideoEncoder_Stop(encoder_);
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "Stop failed, ret: %{public}d", ret);
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t VideoEncoder::Release()
{
    if (encoder_ != nullptr) {
        OH_VideoEncoder_Destroy(encoder_);
        encoder_ = nullptr;
    }
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t VideoEncoder::SetCallback(CodecUserData *codecUserData)
{
    int32_t ret = AV_ERR_OK;
    ret =
        OH_VideoEncoder_RegisterCallback(encoder_,
                                         {SampleCallback::OnCodecError, SampleCallback::OnCodecFormatChange,
                                          SampleCallback::EncOnNeedInputBuffer, SampleCallback::EncOnNewOutputBuffer},
                                         codecUserData);
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "VideoEncoder Set callback failed, ret: %{public}d", ret);
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    
    OH_LOG_INFO(LOG_APP, "====== VideoEncoder SetCallback ======");

    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t VideoEncoder::Configure(const SampleInfo &sampleInfo)
{
    OH_AVFormat *format = OH_AVFormat_Create();
    if (format == nullptr) {
        OH_LOG_ERROR(LOG_APP, "AVFormat create failed");
        return AVCODEC_SAMPLE_ERR_ERROR;
    }

    OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, sampleInfo.videoWidth);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, sampleInfo.videoHeight);
    OH_AVFormat_SetDoubleValue(format, OH_MD_KEY_FRAME_RATE, sampleInfo.frameRate);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, sampleInfo.pixelFormat);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_VIDEO_ENCODE_BITRATE_MODE, sampleInfo.bitrateMode);
    OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, sampleInfo.videoBitrate);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_PROFILE, sampleInfo.hevcProfile);

    if (sampleInfo.isHDRVivid) {
        OH_AVFormat_SetIntValue(format, OH_MD_KEY_I_FRAME_INTERVAL, sampleInfo.iFrameInterval);
        OH_AVFormat_SetIntValue(format, OH_MD_KEY_RANGE_FLAG, sampleInfo.rangFlag);
        OH_AVFormat_SetIntValue(format, OH_MD_KEY_COLOR_PRIMARIES, sampleInfo.primary);
        OH_AVFormat_SetIntValue(format, OH_MD_KEY_TRANSFER_CHARACTERISTICS, sampleInfo.transfer);
        OH_AVFormat_SetIntValue(format, OH_MD_KEY_MATRIX_COEFFICIENTS, sampleInfo.matrix);
    }
    OH_LOG_INFO(LOG_APP, "====== VideoEncoder config ======");
    OH_LOG_INFO(LOG_APP, "%{public}d*%{public}d, %{public}.1ffps", sampleInfo.videoWidth, sampleInfo.videoHeight,
                sampleInfo.frameRate);

    int ret = OH_VideoEncoder_Configure(encoder_, format);
    OH_AVFormat_Destroy(format);
    format = nullptr;
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "Config failed, ret: %{public}d", ret);
        return AVCODEC_SAMPLE_ERR_ERROR;
    }
    return AVCODEC_SAMPLE_ERR_OK;
}